package com.gitee.wsl.jvm.ext.base

import java.text.SimpleDateFormat
import java.time.*
import java.time.format.DateTimeFormatter
import java.time.temporal.*
import java.util.Calendar
import java.util.Date
import java.util.GregorianCalendar
import java.util.Locale

/**
格式化符号表
符号	描述	                             长度	    范围	                示例
d	月份中的第几天，有前导零	          2	      01-31	                02
D	缩写单词表示的周几	                  3 	  Mon-Sun	             Mon
j	月份中的第几天，没有前导零	          -	        1-31	             2
S	第几天的英文缩写后缀，一般和j配合使用	  2	    st/nd/rd/th	            th
l	完整单词表示的周几	                  - 	Monday-Sunday	          Monday
F	完整单词表示的月份	                  -	    January-December	        January
m	数字表示的月份，有前导零	          2	        01-12           	01
M	缩写单词表示的月份	                  3	        Jan-Dec	            Jan
n	数字表示的月份，没有前导零	          -	        1-12	             1
Y	4 位数字完整表示的年份	              4	       0000-9999	        2006
y	2 位数字表示的年份	                   2	    00-99	             06
a	小写的上午和下午标识	                2   	am/pm	                pm
A	大写的上午和下午标识	                2	    AM/PM	            PM
g	小时，12 小时格式	                   -	     1-12	            3
G	小时，24 小时格式	                   -	     0-23	            15
h	小时，12 小时格式	                    2	    00-11	               03
H	小时，24 小时格式	                    2	    00-23           	15
i	分钟	                                2	    01-59	            04
s	秒数	                                2	    01-59	            05
c	ISO8601 格式日期	                    -	    -	                2006-01-02T15:04:05-07:00
r	RFC2822 格式日期	                    -      	-	                Mon, 02 Jan 2006 15:04:05 -0700
O	与格林威治时间相差的小时数	             -	    -	                -0700
P	与格林威治时间相差的小时数，小时和分钟之间有冒号分隔	-	-	            +07:00
T	时区缩写	                            -	    -	                MST
W	ISO8601 格式数字表示的年份中的第几周	    2	    01-52	            01
N	ISO8601 格式数字表示的星期中的第几天	    2	    01-07	             02
L	是否为闰年，如果是闰年为 1，否则为 0	    1	    0-1	                0
U	秒级时间戳	                        10  	-	                1611818268
u	毫秒	                                -	    1-999	            999
w	数字表示的周几	                    1	    0-6	                1
t	月份中的总天数	                    2	    28-31	            31
z	年份中的第几天	                    -	    1-365	               2
e	当前位置	                            -	    -	                America/New_York
Q	当前季节	                            1	    1-4	                    1
C	当前世纪数	                        -	    0-99	            21
 **/
object DateTimeExt{
    // region All-time
    val beginningOfSampleTime: LocalDateTime by lazy{ LocalDateTime.of(1990, 1, 1, 0, 0) }

    val endOfSampleTime: LocalDateTime by lazy { LocalDateTime.of(2050, 1, 1, 0, 0) }
    // endregion


    // a integer to xx:xx:xx
    fun secToTime(time: Int): String {
        var timeStr: String? = null
        var hour = 0
        var minute = 0
        var second = 0
        if (time <= 0) return "00:00:00" else {
            minute = time / 60
            if (minute < 60) {
                second = time % 60
                timeStr = "00:" + unitFormat(minute) + ":" + unitFormat(second)
            } else {
                hour = minute / 60
                if (hour > 99) return "99:59:59"
                minute %= 60
                second = time - hour * 3600 - minute * 60
                timeStr = unitFormat(hour) + ":" +unitFormat(minute) + ":" + unitFormat(second)
            }
        }
        return timeStr
    }

    private fun unitFormat(i: Int): String {
        return  if (i in 0..9) "0$i" else "" + i
    }

    private val CHINESE_ZODIAC = arrayOf("猴", "鸡", "狗", "猪", "鼠", "牛", "虎", "兔", "龙", "蛇", "马", "羊")

    /**
     * detail: 时间格式字符串常量
     * @author Ttt
     */
    object TIME {
        // 一分钟 60 秒
        const val MINUTE_S = 60

        // 一小时 60 * 60 秒
        const val HOUR_S = 3600

        // 一天 24 * 60 * 60 秒
        const val DAY_S = 86400

        // 周与秒的倍数
        const val WEEK_S = DAY_S * 7

        // 月与秒的倍数
        const val MONTH_S = DAY_S * 30

        // 年与秒的倍数
        const val YEAR_S = DAY_S * 365

        // 秒与毫秒的倍数
        const val SECOND_MS: Long = 1000

        // 分与毫秒的倍数
        const val MINUTE_MS = SECOND_MS * 60

        // 时与毫秒的倍数
        const val HOUR_MS = MINUTE_MS * 60

        // 天与毫秒的倍数
        const val DAY_MS = HOUR_MS * 24

        // 周与毫秒的倍数
        const val WEEK_MS = DAY_MS * 7

        // 月与毫秒的倍数
        const val MONTH_MS = DAY_MS * 30

        // 年与毫秒的倍数
        const val YEAR_MS = DAY_MS * 365

        // ==========
        // = 时间格式 =
        // ==========
        const val yy = "yy"
        const val yyyy = "yyyy"
        const val MM = "MM"
        const val dd = "dd"
        const val HH = "HH"
        const val mm = "mm"
        const val ss = "ss"
        const val yyMMdd = "yyMMdd"
        const val yyMMdd_POINT = "yy.MM.dd"
        const val yyMMdd_HYPHEN = "yy-MM-dd"
        const val yyMMdd_UNDERSCORE = "yy_MM_dd"
        const val yyyyMMdd = "yyyyMMdd"
        const val yyyyMMdd_POINT = "yyyy.MM.dd"
        const val yyyyMMdd_HYPHEN = "yyyy-MM-dd"
        const val yyyyMMdd_UNDERSCORE = "yyyy_MM_dd"
        const val yyyyMMddHHmm = "yyyyMMddHHmm"
        const val yyyyMMddHHmm_POINT = "yyyy.MM.dd HH:mm"
        const val yyyyMMddHHmm_HYPHEN = "yyyy-MM-dd HH:mm"
        const val yyyyMMddHHmm_UNDERSCORE = "yyyy_MM_dd HH:mm"
        const val yyyyMMddHHmmss = "yyyyMMddHHmmss"
        const val yyyyMMddHHmmss_POINT = "yyyy.MM.dd HH:mm:ss"
        const val yyyyMMddHHmmss_HYPHEN = "yyyy-MM-dd HH:mm:ss"
        const val yyyyMMddHHmmss_UNDERSCORE = "yyyy_MM_dd HH:mm:ss"
        const val MMdd = "MMdd"
        const val MMdd_POINT = "MM.dd"
        const val MMdd_HYPHEN = "MM-dd"
        const val MMdd_UNDERSCORE = "MM_dd"
        const val HHmm = "HHmm"
        const val HHmm_COLON = "HH:mm"
        const val HHmm_POINT = "HH.mm"
        const val HHmm_HYPHEN = "HH-mm"
        const val HHmm_UNDERSCORE = "HH_mm"
        const val HHmmss = "HHmmss"
        const val HHmmss_COLON = "HH:mm:ss"
        const val HHmmss_POINT = "HH.mm.ss"
        const val HHmmss_HYPHEN = "HH-mm-ss"
        const val HHmmss_UNDERSCORE = "HH_mm_ss"
        const val SPECIAL_mmddHHmmyyyyss = "MMddHHmmyyyy.ss"

        const val RemoteDateFormatWithMilli = "yyyy-MM-dd'T'hh:mm:ss.SSSSSS'Z'"
        const val RemoteDateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'"
        const val RemoteDateFormatWithThreeMilliSecond = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
        const val RemoteDateFormatYear = "yyyy/MM/dd"
        const val LocaleDataFormatWithAmPm = "MMM dd,yyyy H:mm:ss a"


        const val RFC1123 = "EEE, dd MMM yyyy HH:mm:ss 'GMT'"
        //rfc1123.setTimeZone(java.util.TimeZone.getTimeZone("GMT"))

        const val RFC3339 = "yyyy-MM-dd'T'HH:mm:ss'Z'"
        //rfc3339.setTimeZone(java.util.TimeZone.getTimeZone("UTC"))


        // =============
        // = 中文时间格式 =
        // =============
        const val ZH_yy = "yy年"
        const val ZH_yyyy = "yyyy年"
        const val ZH_MM = "MM月"
        const val ZH_dd = "dd日"
        const val ZH_HH = "HH时"
        const val ZH_mm = "mm分"
        const val ZH_ss = "ss秒"
        const val ZH_yyMMdd = "yy年MM月dd日"
        const val ZH_yyyyMMdd = "yyyy年MM月dd日"
        const val ZH_yyyyMMddHHmm = "yyyy年MM月dd日HH时mm分"
        const val ZH_yyyyMMddHHmmss = "yyyy年MM月dd日HH时mm分ss秒"
        const val ZH_MMdd = "MM月dd日"
        const val ZH_HHmm = "HH时mm分"
        const val ZH_HHmmss = "HH时mm分ss秒"
    }

    // 线程安全 SimpleDateFormat Map
    private val SDEF_THREAD_LOCAL: ThreadLocal<MutableMap<String, SimpleDateFormat>> =
        object : ThreadLocal<MutableMap<String, SimpleDateFormat>>() {
            override fun initialValue(): MutableMap<String, SimpleDateFormat> {
                return mutableMapOf()
            }
        }

    val sampleDateTimeFormat : SimpleDateFormat
        get() = getSafeDateFormat(TIME.ZH_yyyyMMddHHmm)
    val sampleDateFormat : SimpleDateFormat
        get() = getSafeDateFormat(TIME.ZH_yyyyMMdd)

    /**
     * 获取对应时间格式线程安全 SimpleDateFormat
     * @param pattern 时间格式
     * @return [SimpleDateFormat]
     */

    @Suppress("SimpleDateFormat")
    fun getSafeDateFormat(pattern: String): SimpleDateFormat {
        val sdfMap: MutableMap<String, SimpleDateFormat> = SDEF_THREAD_LOCAL.get()!!
        var format: SimpleDateFormat? = sdfMap[pattern]
        if (format == null) {
            format = SimpleDateFormat(pattern)
            sdfMap[pattern] = format
        }
        return format
    }

    /**
     * Return the Chinese zodiac.
     *
     * @param date The date.
     * @return the Chinese zodiac
     */
    fun getChineseZodiac(date: Date): String {
        val cal = Calendar.getInstance()
        cal.time = date
        return getChineseZodiac(cal[Calendar.YEAR])
    }

    /**
     * Return the Chinese zodiac.
     *
     * @param year The year.
     * @return the Chinese zodiac
     */
    fun getChineseZodiac(year: Int): String {
        return CHINESE_ZODIAC[year % 12]
    }

    private val ZODIAC_FLAGS = intArrayOf(20, 19, 21, 21, 21, 22, 23, 23, 23, 24, 23, 22)
    private val ZODIAC = arrayOf(
        "水瓶座", "双鱼座", "白羊座", "金牛座", "双子座", "巨蟹座",
        "狮子座", "处女座", "天秤座", "天蝎座", "射手座", "摩羯座"
    )

    /**
     * Return the zodiac.
     *
     * @param date The date.
     * @return the zodiac
     */
    fun getZodiac(date: Date): String {
        val cal = Calendar.getInstance()
        cal.time = date
        val month = cal[Calendar.MONTH] + 1
        val day = cal[Calendar.DAY_OF_MONTH]
        return getZodiac(month, day)
    }

    /**
     * Return the zodiac.
     *
     * @param month The month.
     * @param day   The day.
     * @return the zodiac
     */
    fun getZodiac(month: Int, day: Int): String {
        return ZODIAC[if (day >= ZODIAC_FLAGS[month - 1]) month - 1 else (month + 10) % 12]
    }

    fun millis2FitTimeSpan(millis1: Long, precision0to5:Int): String {
        var millis = millis1
        var precision = precision0to5
        if (precision <= 0) return ""
        precision = precision.coerceAtMost(5)
        val units = arrayOf("天", "小时", "分钟", "秒", "毫秒")
        if (millis == 0L) return 0.toString() + units[precision - 1]
        val sb = StringBuilder()
        if (millis < 0) {
            sb.append("-")
            millis = -millis
        }
        val unitLen = intArrayOf(86400000, 3600000, 60000, 1000, 1)
        for (i in 0 until precision) {
            if (millis >= unitLen[i]) {
                val mode = millis / unitLen[i]
                millis -= mode * unitLen[i]
                sb.append(mode).append(units[i])
            }
        }
        return sb.toString()
    }


    object TimeConstants {
        const val MSEC = 1
        const val SEC = 1000
        const val MIN = 60000
        const val HOUR = 3600000
        const val DAY = 86400000

    }

}

interface TimeProvider {
    fun now(): LocalDateTime
    fun dateNow(): LocalDate
    fun zoneId(): ZoneId
}

class DeviceTimeProvider: TimeProvider {
    override fun now(): LocalDateTime = LocalDateTime.now()

    override fun dateNow(): LocalDate = LocalDate.now()

    override fun zoneId(): ZoneId = ZoneId.systemDefault()
}

var systemTimeProvider:TimeProvider = DeviceTimeProvider()


inline val currentDateTime: LocalDateTime
    get() = systemTimeProvider.now()

inline val currentTimeMillis:Long get() = currentDateTime.toEpochSecond(ZoneOffset.UTC)

inline val today: LocalDate
    get() = systemTimeProvider.dateNow()

inline val startOfToday: LocalDateTime
    get() = today.atStartOfDay()

inline val endOfToday : LocalDateTime
    get() = today.atEndOfDay()

fun LocalDate.atEndOfDay(): LocalDateTime{
    return this.atTime(23, 59, 59)
}

inline val LocalDateTime.atStartOfDay: LocalDateTime
    get() = toLocalDate().atStartOfDay()

inline val LocalDateTime.atEndOfDay: LocalDateTime
    get() = toLocalDate().atEndOfDay()

inline val startOfWeek: LocalDate
    get() = today.atStartOfWeek

inline val endOfWeek : LocalDate
    get() = today.atEndOfWeek


// region Week
inline val LocalDate.atStartOfWeek :LocalDate
    get() = with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY))

inline val LocalDate.atEndOfWeek :LocalDate
    get() =with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY))


fun LocalDateTime.format(pattern: String, locale: Locale? = null): String =
    dateTimeFormatterOf(pattern, locale).format(this)

fun LocalDate.format(pattern: String, locale: Locale? = null): String =
    dateTimeFormatterOf(pattern, locale).format(this)


fun String.toLocalDateTime(pattern: String): LocalDateTime =
    LocalDateTime.parse(this, DateTimeFormatter.ofPattern(pattern))

fun String.toLocalDate(pattern: String): LocalDate =
    LocalDate.parse(this, DateTimeFormatter.ofPattern(pattern))


val LocalDateTime.atFirstDayOfYear: LocalDateTime get() = with(TemporalAdjusters.firstDayOfYear())

val LocalDateTime.atLastDayOfYear: LocalDateTime get()= with(TemporalAdjusters.lastDayOfYear())

val LocalDateTime.atFirstDayOfNextYear: LocalDateTime get()= with(TemporalAdjusters.firstDayOfNextYear())

val LocalDateTime.atFirstDayOfLastYear: LocalDateTime get()= with { it.with(ChronoField.DAY_OF_YEAR, 1).minus(1,
    ChronoUnit.YEARS
) }

val LocalDateTime.atFirstDayOfMonth: LocalDateTime get()= with(TemporalAdjusters.firstDayOfMonth())

val LocalDateTime.lastDayOfMonth: LocalDateTime get()= with(TemporalAdjusters.lastDayOfMonth())

val LocalDateTime.atFirstDayOfNextMonth: LocalDateTime get()= with(TemporalAdjusters.firstDayOfNextMonth())

val LocalDateTime.atFirstDayOfLastMonth: LocalDateTime get()= with { it.with(ChronoField.DAY_OF_MONTH, 1).minus(1,
    ChronoUnit.MONTHS
) }

fun LocalDateTime.atFirstInMonth(dayOfWeek: DayOfWeek): LocalDateTime = with(TemporalAdjusters.firstInMonth(dayOfWeek))

fun LocalDateTime.atLastInMonth(dayOfWeek: DayOfWeek): LocalDateTime = with(TemporalAdjusters.lastInMonth(dayOfWeek))

fun LocalDateTime.atDayOfWeekInMonth(ordinal: Int, dayOfWeek: DayOfWeek): LocalDateTime = with(TemporalAdjusters.dayOfWeekInMonth(ordinal, dayOfWeek))

fun LocalDateTime.next(dayOfWeek: DayOfWeek): LocalDateTime = with(TemporalAdjusters.next(dayOfWeek))

fun LocalDateTime.nextOrSame(dayOfWeek: DayOfWeek): LocalDateTime = with(TemporalAdjusters.nextOrSame(dayOfWeek))

fun LocalDateTime.previous(dayOfWeek: DayOfWeek): LocalDateTime = with(TemporalAdjusters.previous(dayOfWeek))

fun LocalDateTime.previousOrSame(dayOfWeek: DayOfWeek): LocalDateTime = with(TemporalAdjusters.previousOrSame(dayOfWeek))

val LocalDate.atFirstDayOfYear: LocalDate get()= with(TemporalAdjusters.firstDayOfYear())

val LocalDate.atLastDayOfYear: LocalDate get()= with(TemporalAdjusters.lastDayOfYear())

val LocalDate.atFirstDayOfNextYear: LocalDate get()= with(TemporalAdjusters.firstDayOfNextYear())

val LocalDate.atFirstDayOfLastYear: LocalDate get()= with { it.with(ChronoField.DAY_OF_YEAR, 1).minus(1,
    ChronoUnit.YEARS
) }

val LocalDate.atFirstDayOfMonth: LocalDate get()= with(TemporalAdjusters.firstDayOfMonth())

val LocalDate.atLastDayOfMonth: LocalDate get()= with(TemporalAdjusters.lastDayOfMonth())

val LocalDate.atFirstDayOfNextMonth: LocalDate get()= with(TemporalAdjusters.firstDayOfNextMonth())

val LocalDate.atFirstDayOfLastMonth: LocalDate get()= with { it.with(ChronoField.DAY_OF_MONTH, 1).minus(1,
    ChronoUnit.MONTHS
) }

fun LocalDate.atFirstInMonth(dayOfWeek: DayOfWeek): LocalDate = with(TemporalAdjusters.firstInMonth(dayOfWeek))

fun LocalDate.atLastInMonth(dayOfWeek: DayOfWeek): LocalDate = with(TemporalAdjusters.lastInMonth(dayOfWeek))

fun LocalDate.atDayOfWeekInMonth(ordinal: Int, dayOfWeek: DayOfWeek): LocalDate = with(TemporalAdjusters.dayOfWeekInMonth(ordinal, dayOfWeek))

fun LocalDate.next(dayOfWeek: DayOfWeek): LocalDate = with(TemporalAdjusters.next(dayOfWeek))

fun LocalDate.nextOrSame(dayOfWeek: DayOfWeek): LocalDate = with(TemporalAdjusters.nextOrSame(dayOfWeek))

fun LocalDate.previous(dayOfWeek: DayOfWeek): LocalDate = with(TemporalAdjusters.previous(dayOfWeek))

fun LocalDate.previousOrSame(dayOfWeek: DayOfWeek): LocalDate = with(TemporalAdjusters.previousOrSame(dayOfWeek))

fun dateTimeFormatterOf(pattern: String, locale: Locale?): DateTimeFormatter =
    if (locale != null) {
        DateTimeFormatter.ofPattern(pattern, locale)
    } else {
        DateTimeFormatter.ofPattern(pattern)
    }

/**
 * Return whether it is am.
 *
 * @return `true`: yes<br></br>`false`: no
 */
val isAm: Boolean
    get() {
        val cal = Calendar.getInstance()
        return cal[GregorianCalendar.AM_PM] == 0
    }

/**
 * Return whether it is am.
 *
 * @return `true`: yes<br></br>`false`: no
 */
val isPm: Boolean
    get() = !isAm


fun LocalDate.dateId() = format("yyyy-MM-dd")


val Long.nanos get() = Duration.ofNanos(this)!!
val Long.micros get() = Duration.ofNanos(this * 1000L)
val Long.millis get() = Duration.ofMillis(this)!!
val Long.seconds get() = Duration.ofSeconds(this)!!
val Long.minutes get() = Duration.ofMinutes(this)!!
val Long.hours get() = Duration.ofHours(this)!!
val Int.nanos get() = toLong().nanos
val Int.micros get() = toLong().micros
val Int.millis get() = toLong().millis
val Int.seconds get() = toLong().seconds
val Int.minutes get() = toLong().minutes
val Int.hours get() = toLong().hours
val Double.nanos get() = this.toLong().nanos
val Double.micros get() = (this * 1000).nanos
val Double.millis get() = (this * 1000000).nanos
val Double.seconds: Duration get() = this.toLong().let { s -> s.seconds + ((this - s) * 1000000000).nanos }
val Double.minutes get() = (this * 60).seconds
val Double.hours get() = (this * 3600).seconds

val Int.days get() = Period.ofDays(this)!!
val Int.weeks get() = Period.ofWeeks(this)!!
val Int.months get() = Period.ofMonths(this)!!
val Int.years get() = Period.ofYears(this)!!
val Long.days get() = toInt().days
val Long.weeks get() = toInt().weeks
val Long.months get() = toInt().months
val Long.years get() = toInt().years

val Int.asYear get() = Year.of(this)!!
val Int.asMonth get() = Month.of(this)!!
val Int.asDayOfWeek get() = DayOfWeek.of(this)!!

// java.time.Duration already implements binary +, -, <, <=, ==, >=, >
operator fun Duration.unaryMinus() = this.negated()!!
operator fun Duration.times(multiplicand: Long) = this.multipliedBy(multiplicand)!!
operator fun Duration.div(divisor: Long) = this.dividedBy(divisor)!!
operator fun Duration.component1() = this.seconds
operator fun Duration.component2() = this.nano

operator fun Period.unaryMinus() = this.negated()!!
operator fun Period.times(multiplicand: Int) = this.multipliedBy(multiplicand)!!
operator fun Period.component1() = this.years
operator fun Period.component2() = this.months
operator fun Period.component3() = this.days

operator fun Year.plus(month: Month): YearMonth = atMonth(month)
operator fun Year.plus(monthDay: MonthDay): LocalDate = atMonthDay(monthDay)
operator fun Year.plus(years: Int): Year = plusYears(years.toLong())
operator fun Year.minus(years: Int): Year = minusYears(years.toLong())

operator fun Year.inc(): Year = plusYears(1L)
operator fun Year.dec(): Year = minusYears(1L)
operator fun Month.inc(): Month = plus(1L)
operator fun Month.dec(): Month = minus(1L)
operator fun DayOfWeek.inc(): DayOfWeek = plus(1L)
operator fun DayOfWeek.dec(): DayOfWeek = minus(1L)

val TemporalAmount.ago get() = (OffsetDateTime.now(ZoneOffset.UTC) - this)!!
val TemporalAmount.fromNow get() = (OffsetDateTime.now(ZoneOffset.UTC) + this)!!

// temporal queries

val TemporalAccessor.precision: TemporalUnit? get() = query(TemporalQueries.precision())
val TemporalAccessor.yr: Year? get() = query(Year::from)
val TemporalAccessor.yearMonth: YearMonth? get() = query(YearMonth::from)
val TemporalAccessor.month: Month? get() = query(Month::from)
val TemporalAccessor.monthDay: MonthDay? get() = query(MonthDay::from)
val TemporalAccessor.dayOfWeek: DayOfWeek? get() = query(DayOfWeek::from)
val TemporalAccessor.instant: Instant? get() = query(Instant::from)
val TemporalAccessor.localDate: LocalDate? get() = query(LocalDate::from)
val TemporalAccessor.localTime: LocalTime? get() = query(LocalTime::from)
val TemporalAccessor.localDateTime: LocalDateTime? get() = query(LocalDateTime::from)
val TemporalAccessor.zoneOffset: ZoneOffset? get() = query(ZoneOffset::from)
val TemporalAccessor.offsetTime: OffsetTime? get() = query(OffsetTime::from)
val TemporalAccessor.offsetDateTime: OffsetDateTime? get() = query(OffsetDateTime::from)
val TemporalAccessor.zone: ZoneId? get() = query(TemporalQueries.zone())
val TemporalAccessor.zoneId: ZoneId? get() = query(ZoneId::from)
val TemporalAccessor.zonedDateTime: ZonedDateTime? get() = query(ZonedDateTime::from)

// temporal adjusters

val <T: Temporal> T.atStartOfMonth get() = with(TemporalAdjusters.firstDayOfMonth())!!
val <T: Temporal> T.atEndOfMonth get() = with(TemporalAdjusters.lastDayOfMonth())!!
val <T: Temporal> T.atStartOfNextMonth get() = with(TemporalAdjusters.firstDayOfNextMonth())!!
val <T: Temporal> T.atStartOfYear get() = with(TemporalAdjusters.firstDayOfYear())!!
val <T: Temporal> T.atEndOfYear get() = with(TemporalAdjusters.lastDayOfYear())!!
val <T: Temporal> T.atStartOfNextYear get() = with(TemporalAdjusters.firstDayOfNextYear())!!

infix fun TemporalAccessor.supports(temporalField: TemporalField) = isSupported(temporalField)
infix fun Temporal.supports(temporalUnit: TemporalUnit) = isSupported(temporalUnit)

abstract class TemporalRange<T>(override val start: T, override val endInclusive: T, val step: Long = 1L) : ClosedRange<T>, Iterable<T>
        where T : Temporal, T : Comparable<T> {
    abstract infix fun unit(newUnit: TemporalUnit?): TemporalRange<T>
    abstract infix fun step(newStep: Long): TemporalRange<T>
}

class TemporalRangeInclusive<T>(start: T, endInclusive: T, unit: TemporalUnit? = null, step: Long = 1L) :
    TemporalRange<T>(start, endInclusive, step) where T : Temporal, T : Comparable<T> {

    val unit: TemporalUnit = unit ?: start.precision!!

    override fun iterator(): Iterator<T> = TemporalRangeIteratorInclusive()

    inner class TemporalRangeIteratorInclusive : Iterator<T> {
        private var current = start

        init {
            when {
                step == 0L -> throw IllegalArgumentException("Step is 0")
                step < 0L && start < endInclusive -> throw IllegalArgumentException("Step incompatible with ordering of start and end values")
                step > 0L && start > endInclusive -> throw IllegalArgumentException("Start and end values out of order")
            }

            if (!start.isSupported(unit)) {
                throw IllegalArgumentException("Value does not support unit $unit")
            }
        }

        override fun hasNext() = if (step < 0) current >= endInclusive else current <= endInclusive

        override fun next(): T {
            val next = current
            @Suppress("UNCHECKED_CAST")
            current = current.plus(step, unit) as T
            return next
        }
    }

    override infix fun unit(newUnit: TemporalUnit?) = TemporalRangeInclusive(start, endInclusive, newUnit, step)
    override infix fun step(newStep: Long) = TemporalRangeInclusive(start, endInclusive, unit, newStep)
}

class TemporalRangeExclusive<T>(start: T, endInclusive: T, unit: TemporalUnit? = null, step: Long = 1L) :
    TemporalRange<T>(start, endInclusive, step) where T : Temporal, T : Comparable<T> {

    val unit: TemporalUnit = unit ?: start.precision!!

    override fun iterator(): Iterator<T> = TemporalRangeIteratorExclusive()

    inner class TemporalRangeIteratorExclusive : Iterator<T> {
        private var current = start

        init {
            when {
                step == 0L -> throw IllegalArgumentException("Step is 0")
                step < 0L && start < endInclusive -> throw IllegalArgumentException("Step incompatible with ordering of start and end values")
                step > 0L && start > endInclusive -> throw IllegalArgumentException("Start and end values out of order")
            }

            if (!start.isSupported(unit)) {
                throw IllegalArgumentException("Value does not support unit $unit")
            }
        }

        override fun hasNext() = if (step < 0) current > endInclusive else current < endInclusive

        override fun next(): T {
            val next = current
            @Suppress("UNCHECKED_CAST")
            current = current.plus(step, unit) as T
            return next
        }
    }

    override infix fun unit(newUnit: TemporalUnit?) = TemporalRangeExclusive(start, endInclusive, newUnit, step)
    override infix fun step(newStep: Long) = TemporalRangeExclusive(start, endInclusive, unit, newStep)
}

operator fun <T> T.rangeTo(other: T): TemporalRange<T> where T : Temporal, T : Comparable<T> = TemporalRangeInclusive(this, other)
infix fun <T> T.until(other: T): TemporalRange<T> where T : Temporal, T : Comparable<T> = TemporalRangeExclusive(this, other)
